package cn.dansj.common.utils.jdk;

import cn.dansj.common.utils.transfer.ArrayUtils;

import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;

public class Preconditions {
    private static RuntimeException outOfBounds(BiFunction<String, List<Number>, ? extends RuntimeException> oobef, String checkKind, Number... args) {
        List<Number> largs = ArrayUtils.asList(args);
        RuntimeException e = oobef == null ? null : oobef.apply(checkKind, largs);
        return e == null ? new IndexOutOfBoundsException(outOfBoundsMessage(checkKind, largs)) : e;
    }

    private static RuntimeException outOfBoundsCheckIndex(BiFunction<String, List<Number>, ? extends RuntimeException> oobe, int index, int length) {
        return outOfBounds(oobe, "checkIndex", index, length);
    }

    private static RuntimeException outOfBoundsCheckFromToIndex(BiFunction<String, List<Number>, ? extends RuntimeException> oobe, int fromIndex, int toIndex, int length) {
        return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length);
    }

    private static RuntimeException outOfBoundsCheckFromIndexSize(BiFunction<String, List<Number>, ? extends RuntimeException> oobe, int fromIndex, int size, int length) {
        return outOfBounds(oobe, "checkFromIndexSize", fromIndex, size, length);
    }

    private static RuntimeException outOfBoundsCheckIndex(BiFunction<String, List<Number>, ? extends RuntimeException> oobe, long index, long length) {
        return outOfBounds(oobe, "checkIndex", index, length);
    }

    private static RuntimeException outOfBoundsCheckFromToIndex(BiFunction<String, List<Number>, ? extends RuntimeException> oobe, long fromIndex, long toIndex, long length) {
        return outOfBounds(oobe, "checkFromToIndex", fromIndex, toIndex, length);
    }

    private static RuntimeException outOfBoundsCheckFromIndexSize(BiFunction<String, List<Number>, ? extends RuntimeException> oobe, long fromIndex, long size, long length) {
        return outOfBounds(oobe, "checkFromIndexSize", fromIndex, size, length);
    }

    public static <X extends RuntimeException> BiFunction<String, List<Number>, X> outOfBoundsExceptionFormatter(Function<String, X> f) {
        return (checkKind, args) -> f.apply(outOfBoundsMessage(checkKind, args));
    }

    private static String outOfBoundsMessage(String checkKind, List<? extends Number> args) {
        if (checkKind == null && args == null) {
            return "Range check failed";
        } else if (checkKind == null) {
            return String.format("Range check failed: %s", args);
        } else if (args == null) {
            return String.format("Range check failed: %s", checkKind);
        }

        int argSize = 0;
        switch (checkKind) {
            case "checkIndex":
                argSize = 2;
                break;
            case "checkFromToIndex":
            case "checkFromIndexSize":
                argSize = 3;
                break;
            default:
        }

        // Switch to default if fewer or more arguments than required are supplied
        switch ((args.size() != argSize) ? "" : checkKind) {
            case "checkIndex":
                return String.format("Index %s out of bounds for length %s", args.get(0), args.get(1));
            case "checkFromToIndex":
                return String.format("Range [%s, %s) out of bounds for length %s", args.get(0), args.get(1), args.get(2));
            case "checkFromIndexSize":
                return String.format("Range [%s, %<s + %s) out of bounds for length %s", args.get(0), args.get(1), args.get(2));
            default:
                return String.format("Range check failed: %s %s", checkKind, args);
        }
    }

    public static <X extends RuntimeException> int checkIndex(int index, int length, BiFunction<String, List<Number>, X> oobef) {
        if (index < 0 || index >= length) throw outOfBoundsCheckIndex(oobef, index, length);
        return index;
    }

    public static <X extends RuntimeException> int checkFromToIndex(int fromIndex, int toIndex, int length, BiFunction<String, List<Number>, X> oobef) {
        if (fromIndex < 0 || fromIndex > toIndex || toIndex > length) {
            throw outOfBoundsCheckFromToIndex(oobef, fromIndex, toIndex, length);
        }
        return fromIndex;
    }

    public static <X extends RuntimeException> int checkFromIndexSize(int fromIndex, int size, int length, BiFunction<String, List<Number>, X> oobef) {
        if ((length | fromIndex | size) < 0 || size > length - fromIndex) {
            throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length);
        }
        return fromIndex;
    }

    public static <X extends RuntimeException> long checkIndex(long index, long length, BiFunction<String, List<Number>, X> oobef) {
        if (index < 0 || index >= length) {
            throw outOfBoundsCheckIndex(oobef, index, length);
        }
        return index;
    }

    public static <X extends RuntimeException> long checkFromToIndex(long fromIndex, long toIndex, long length, BiFunction<String, List<Number>, X> oobef) {
        if (fromIndex < 0 || fromIndex > toIndex || toIndex > length) {
            throw outOfBoundsCheckFromToIndex(oobef, fromIndex, toIndex, length);
        }
        return fromIndex;
    }

    public static <X extends RuntimeException> long checkFromIndexSize(long fromIndex, long size, long length, BiFunction<String, List<Number>, X> oobef) {
        if ((length | fromIndex | size) < 0 || size > length - fromIndex) {
            throw outOfBoundsCheckFromIndexSize(oobef, fromIndex, size, length);
        }
        return fromIndex;
    }
}